Overview

Part 2: 2-D plots

R

Preparing the Data

Imports


library(tidyverse)
── Attaching core tidyverse packages ──────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.3     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.3     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.0
✔ purrr     1.0.2     ── Conflicts ────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(plotly)

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
exif <- read_csv("capstone_exif.csv")
Rows: 1116 Columns: 15── Column specification ────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (10): DateTimeOriginal, CreateDate, ModifyDate, Software, LensInfo, LensModel, ExposureTime, FocalLength, FocalLengthIn3...
dbl  (5): FlickrID, JFIFVersion, ISO, FNumber, BrightnessValue
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
img_data <- read_csv("capstone_img_data.csv")
Rows: 10955 Columns: 87── Column specification ────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (16): flickr, img_loc, the_image, crop_coords, center_rgb, post_top_hsl, post_2_hsl, post_3_hsl, post_4_hsl, post_5_hsl,...
dbl (70): using_id, img_width, img_height, do_img_at, sub_img, full_id, r_min, r_max, r_mean, r_mode, g_min, g_max, g_mean, ...
num  (1): vivid_count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
spec(exif)
cols(
  FlickrID = col_double(),
  DateTimeOriginal = col_character(),
  CreateDate = col_character(),
  ModifyDate = col_character(),
  Software = col_character(),
  LensInfo = col_character(),
  LensModel = col_character(),
  JFIFVersion = col_double(),
  ISO = col_double(),
  ExposureTime = col_character(),
  FNumber = col_double(),
  FocalLength = col_character(),
  FocalLengthIn35mmFormat = col_character(),
  BrightnessValue = col_double(),
  SubjectArea = col_character()
)
spec(img_data)
cols(
  flickr = col_character(),
  using_id = col_double(),
  img_loc = col_character(),
  the_image = col_character(),
  img_width = col_double(),
  img_height = col_double(),
  crop_coords = col_character(),
  do_img_at = col_double(),
  sub_img = col_double(),
  full_id = col_double(),
  r_min = col_double(),
  r_max = col_double(),
  r_mean = col_double(),
  r_mode = col_double(),
  g_min = col_double(),
  g_max = col_double(),
  g_mean = col_double(),
  g_mode = col_double(),
  b_min = col_double(),
  b_max = col_double(),
  b_mean = col_double(),
  b_mode = col_double(),
  center_rgb = col_character(),
  post_num_regions = col_double(),
  post_top_hsl = col_character(),
  post_top_count = col_double(),
  post_2_hsl = col_character(),
  post_2_count = col_double(),
  post_3_hsl = col_character(),
  post_3_count = col_double(),
  post_4_hsl = col_character(),
  post_4_count = col_double(),
  post_5_hsl = col_character(),
  post_5_count = col_double(),
  post_6_hsl = col_character(),
  post_6_count = col_double(),
  center_hsl = col_character(),
  full_red_count = col_double(),
  visib_red_count = col_double(),
  vivid_red_count = col_double(),
  full_orange_count = col_double(),
  visib_orange_count = col_double(),
  vivid_orange_count = col_double(),
  full_yellow_count = col_double(),
  visib_yellow_count = col_double(),
  vivid_yellow_count = col_double(),
  full_green_count = col_double(),
  visib_green_count = col_double(),
  vivid_green_count = col_double(),
  full_cyan_count = col_double(),
  visib_cyan_count = col_double(),
  vivid_cyan_count = col_double(),
  full_blue_count = col_double(),
  visib_blue_count = col_double(),
  vivid_blue_count = col_double(),
  full_purple_count = col_double(),
  visib_purple_count = col_double(),
  vivid_purple_count = col_double(),
  full_mag_count = col_double(),
  visib_mag_count = col_double(),
  vivid_mag_count = col_double(),
  vivid_count = col_number(),
  sat_min_val = col_double(),
  sat_25_val = col_double(),
  sat_50_val = col_double(),
  sat_75_val = col_double(),
  sat_max_val = col_double(),
  hue_mean_val = col_double(),
  sat_mean_val = col_double(),
  light_mean_val = col_double(),
  light_max_val = col_double(),
  light_max_count = col_double(),
  light_min_val = col_double(),
  light_min_count = col_double(),
  light_25_value = col_double(),
  light_50_value = col_double(),
  light_75_value = col_double(),
  gen_bright_count = col_double(),
  gen_dark_count = col_double(),
  common_hsl_1_val = col_character(),
  common_hsl_1_count = col_double(),
  common_hsl_2_val = col_character(),
  common_hsl_2_count = col_double(),
  common_hsl_3_val = col_character(),
  common_hsl_3_count = col_double(),
  common_hsl_4_val = col_character(),
  common_hsl_4_count = col_double()
)

Cleaning

Pre Import:
Sub-image data for main images (0) was bugged in the first hours of image processing. This was fixed in Excel during the data collation stage.

[Editors Note: numeric values looked consistent in Excel and R Studio, but were interpreted as characters, necessitating a large amount of manual re-typing in part 2. At the time it seemed faster than backing up and figuring out the ‘right’ way to fix them]

EXIF:

  • Fix column headers
  • Drop columns: date_time_original, modify_date, lens_info, f_number, focal_length
  • NA values - JFIF <- 0,  subject_area <- >depends on data type< “0 0 0 0”
names(exif) <- gsub("([a-z0-9])([A-Z])", "\\1_\\2", names(exif))
names(exif) <- names(exif) %>% tolower()

exif_tidy <- select(exif, -c(date_time_original, modify_date, lens_info, fnumber, focal_length))
exif_tidy <- replace_na(exif_tidy, list(subject_area = "0 0 0 0", jfifversion = 0))

img_data:

  • Drop columns: flickr, img_loc, the_image, crop_coords, r_mode, b_mode, g_mode, img_height, img_width, do_img_at
  • Na values - post_2-6_hsl <- (-1,-1,-1)
imgsd_tidy <- select(img_data, -c(flickr, img_loc, the_image, img_width, img_height, crop_coords, do_img_at, r_mode, b_mode, g_mode))

imgsd_tidy <- replace_na(imgsd_tidy, list(
  post_2_hsl = "(-1, -1, -1)",
  post_3_hsl = "(-1, -1, -1)",
  post_4_hsl = "(-1, -1, -1)",
  post_5_hsl = "(-1, -1, -1)",
  post_6_hsl = "(-1, -1, -1)"
  )
  )

Basic Feature Engineering

EXIF

  • Split date_time_original to year, month-day, time columns
  • Date column uses a placeholder year so month-to-month comparisons are consistent

exif_tidy <- exif_tidy %>% separate(create_date, into = c('date', 'time'), sep = " ", remove = TRUE) %>% separate(date, into = c('year', 'month', 'day'), sep = ":") 

exif_tidy$date <- as.Date(paste("1881", exif_tidy$month, exif_tidy$day, sep = "-"), format ="%Y-%m-%d")

Img_data

  • Count by flickr id

    • Flag counts under 10
    • Flag counts under 6 (ie: subimage 5 not available)
    • Output list of all flickrIDs with less than 10 records for additional (future) processing

subimg_qty <- imgsd_tidy %>% count(using_id)

To my (happy) surprise, only 5 images have less than 10 results and only 2 have less than 6. In the interest of time, I’m noting these IDs by hand and simply removing them from my working data


good_ids <- subimg_qty[subimg_qty$n >=6, "using_id"]

imgsd_tidy <- imgsd_tidy %>% filter(using_id %in% good_ids$using_id)

exif_tidy <- exif_tidy %>% filter(flickr_id %in% good_ids$using_id)
  • Total pixels = full_(hue)_count(s) (dimension data incorrect for first 1400 records)

imgsd_tidy <- imgsd_tidy %>% mutate(total_pixels = full_red_count +
                                      full_orange_count +
                                      full_yellow_count +
                                      full_green_count +
                                      full_cyan_count +
                                      full_blue_count + 
                                      full_purple_count +
                                      full_mag_count)
  • Split pixel lists/tuples for 3-d mapping
# remove brackets from string-encapsulated lists
imgsd_tidy$center_hsl <- str_replace(imgsd_tidy$center_hsl, '\\[|\\]', '')

# remove parens from string-encapsulated tuples
imgsd_tidy <- imgsd_tidy %>% mutate_all(~ gsub('\\(|\\)', '', .))

# separate pixel values into individual columns
imgsd_split <- imgsd_tidy %>% 
  separate(center_rgb, 
           into = c('center_r', 'center_g', 'center_b'), 
           sep = ',') %>%
  separate(post_top_hsl, 
           into = c('post_top_hue', 'post_top_sat', 'post_top_light'), 
           sep = ',') %>%
  separate(post_2_hsl, 
           into = c('post_2_hue', 'post_2_sat', 'post_2_light'), 
           sep = ',') %>%
  separate(post_3_hsl, 
           into = c('post_3_hue', 'post_3_sat', 'post_3_light'), 
           sep = ',') %>%  
  separate(post_4_hsl, 
           into = c('post_4_hue', 'post_4_sat', 'post_4_light'), 
           sep = ',') %>%
  separate(post_5_hsl, 
           into = c('post_5_hue', 'post_5_sat', 'post_5_light'), 
           sep = ',') %>%
  separate(post_6_hsl, 
           into = c('post_6_hue', 'post_6_sat', 'post_6_light'), 
           sep = ',') %>%
  separate(center_hsl, 
           into = c('center_hue', 'center_sat', 'center_light'), 
           sep = ',') %>%
  separate(common_hsl_1_val, 
           into = c('common_hsl_1_hue', 'common_hsl_1_sat', 'common_hsl_1_light'), 
           sep = ',') %>%
  separate(common_hsl_2_val, 
           into = c('common_hsl_2_hue', 'common_hsl_2_sat', 'common_hsl_2_light'), 
           sep = ',') %>%
  separate(common_hsl_3_val, 
           into = c('common_hsl_3_hue', 'common_hsl_3_sat', 'common_hsl_3_light'), 
           sep = ',') %>%
  separate(common_hsl_4_val, 
           into = c('common_hsl_4_hue', 'common_hsl_4_sat', 'common_hsl_4_light'), 
           sep = ',')

… I probably should have saved those independently during the python image processing stage.

  • To be added as needed:

    • Using total_pixels to calculate specific ratios

Exploration

2D Scatter


imgsd_split$common_hsl_4_hue <- as.numeric(imgsd_split$common_hsl_4_hue)
imgsd_split$common_hsl_4_sat <- as.numeric(imgsd_split$common_hsl_4_sat)

fig <- plot_ly(imgsd_split, 
               x=~common_hsl_4_hue, 
               y= ~common_hsl_4_sat, 
               type = "scatter", mode="markers", size = 2, color = ~sub_img, colors = custom_colors)

fig
Warning: Ignoring 2 observationsWarning: Ignoring 2 observations

Observation: Here we can more closely examine the hue trends present in the images. I’m fascinated by the cluster at 105 hue/60-80 saturation which is present in many sub-images but not 3, 9, or 6.

  • Light min x light max

imgsd_split$light_min_val <- as.numeric(imgsd_split$light_min_val)
imgsd_split$light_max_val <- as.numeric(imgsd_split$light_max_val)

fig <- plot_ly(imgsd_split, 
               x=~light_min_val, 
               y= ~light_max_val, 
               type = "scatter", mode="markers", 
               size = 2, color = ~sub_img, colors = custom_colors)

fig
  • Gen bright x gen dark
  • Sat min x sat max

imgsd_split$sat_min_val <- as.numeric(imgsd_split$sat_min_val)
imgsd_split$sat_max_val <- as.numeric(imgsd_split$sat_max_val)

fig <- plot_ly(imgsd_split, 
               x=~sat_min_val, 
               y= ~sat_max_val, 
               type = "scatter", mode="markers", 
               size = 2, color = ~sub_img, colors = custom_colors)

fig
NA
LS0tDQp0aXRsZTogIkRhdGFzZXQgQ2Fwc3RvbmUiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIE92ZXJ2aWV3DQoNCiMjIFBhcnQgMjogMi1EIHBsb3RzDQoNCiMgUg0KDQojIyBQcmVwYXJpbmcgdGhlIERhdGENCg0KIyMjIEltcG9ydHMNCg0KYGBge3J9DQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShwbG90bHkpDQoNCmV4aWYgPC0gcmVhZF9jc3YoImNhcHN0b25lX2V4aWYuY3N2IikNCmltZ19kYXRhIDwtIHJlYWRfY3N2KCJjYXBzdG9uZV9pbWdfZGF0YS5jc3YiKQ0KDQpgYGANCg0KIyMjIENsZWFuaW5nDQoNCioqUHJlIEltcG9ydDoqKlwNClN1Yi1pbWFnZSBkYXRhIGZvciBtYWluIGltYWdlcyAoMCkgd2FzIGJ1Z2dlZCBpbiB0aGUgZmlyc3QgaG91cnMgb2YgaW1hZ2UgcHJvY2Vzc2luZy4gVGhpcyB3YXMgZml4ZWQgaW4gRXhjZWwgZHVyaW5nIHRoZSBkYXRhIGNvbGxhdGlvbiBzdGFnZS4NCg0KW0VkaXRvcnMgTm90ZTogbnVtZXJpYyB2YWx1ZXMgbG9va2VkIGNvbnNpc3RlbnQgaW4gRXhjZWwgYW5kIFIgU3R1ZGlvLCBidXQgd2VyZSBpbnRlcnByZXRlZCBhcyBjaGFyYWN0ZXJzLCBuZWNlc3NpdGF0aW5nIGEgbGFyZ2UgYW1vdW50IG9mIG1hbnVhbCByZS10eXBpbmcgaW4gcGFydCAyLiBBdCB0aGUgdGltZSBpdCBzZWVtZWQgZmFzdGVyIHRoYW4gYmFja2luZyB1cCBhbmQgZmlndXJpbmcgb3V0IHRoZSAncmlnaHQnIHdheSB0byBmaXggdGhlbV0NCg0KKipFWElGOioqDQoNCi0gICBGaXggY29sdW1uIGhlYWRlcnMNCi0gICBEcm9wIGNvbHVtbnM6IGRhdGVfdGltZV9vcmlnaW5hbCwgbW9kaWZ5X2RhdGUsIGxlbnNfaW5mbywgZl9udW1iZXIsIGZvY2FsX2xlbmd0aA0KLSAgIE5BIHZhbHVlcyAtIEpGSUYgXDwtIDAswqAgc3ViamVjdF9hcmVhIFw8LSBcPmRlcGVuZHMgb24gZGF0YSB0eXBlXDwgIjAgMCAwIDAiDQoNCmBgYHtyfQ0KbmFtZXMoZXhpZikgPC0gZ3N1YigiKFthLXowLTldKShbQS1aXSkiLCAiXFwxX1xcMiIsIG5hbWVzKGV4aWYpKQ0KbmFtZXMoZXhpZikgPC0gbmFtZXMoZXhpZikgJT4lIHRvbG93ZXIoKQ0KDQpleGlmX3RpZHkgPC0gc2VsZWN0KGV4aWYsIC1jKGRhdGVfdGltZV9vcmlnaW5hbCwgbW9kaWZ5X2RhdGUsIGxlbnNfaW5mbywgZm51bWJlciwgZm9jYWxfbGVuZ3RoKSkNCmV4aWZfdGlkeSA8LSByZXBsYWNlX25hKGV4aWZfdGlkeSwgbGlzdChzdWJqZWN0X2FyZWEgPSAiMCAwIDAgMCIsIGpmaWZ2ZXJzaW9uID0gMCkpDQoNCmBgYA0KDQoqKmltZ19kYXRhOioqDQoNCi0gICBEcm9wIGNvbHVtbnM6IGZsaWNrciwgaW1nX2xvYywgdGhlX2ltYWdlLCBjcm9wX2Nvb3Jkcywgcl9tb2RlLCBiX21vZGUsIGdfbW9kZSwgaW1nX2hlaWdodCwgaW1nX3dpZHRoLCBkb19pbWdfYXQNCi0gICBOYSB2YWx1ZXMgLSBwb3N0XzItNl9oc2wgXDwtICgtMSwtMSwtMSkNCg0KYGBge3J9DQppbWdzZF90aWR5IDwtIHNlbGVjdChpbWdfZGF0YSwgLWMoZmxpY2tyLCBpbWdfbG9jLCB0aGVfaW1hZ2UsIGltZ193aWR0aCwgaW1nX2hlaWdodCwgY3JvcF9jb29yZHMsIGRvX2ltZ19hdCwgcl9tb2RlLCBiX21vZGUsIGdfbW9kZSkpDQoNCmltZ3NkX3RpZHkgPC0gcmVwbGFjZV9uYShpbWdzZF90aWR5LCBsaXN0KA0KICBwb3N0XzJfaHNsID0gIigtMSwgLTEsIC0xKSIsDQogIHBvc3RfM19oc2wgPSAiKC0xLCAtMSwgLTEpIiwNCiAgcG9zdF80X2hzbCA9ICIoLTEsIC0xLCAtMSkiLA0KICBwb3N0XzVfaHNsID0gIigtMSwgLTEsIC0xKSIsDQogIHBvc3RfNl9oc2wgPSAiKC0xLCAtMSwgLTEpIg0KICApDQogICkNCg0KYGBgDQoNCiMjIyBCYXNpYyBGZWF0dXJlIEVuZ2luZWVyaW5nDQoNCioqRVhJRioqDQoNCi0gICBTcGxpdCBkYXRlX3RpbWVfb3JpZ2luYWwgdG8geWVhciwgbW9udGgtZGF5LCB0aW1lIGNvbHVtbnMNCi0gICBEYXRlIGNvbHVtbiB1c2VzIGEgcGxhY2Vob2xkZXIgeWVhciBzbyBtb250aC10by1tb250aCBjb21wYXJpc29ucyBhcmUgY29uc2lzdGVudA0KDQpgYGB7cn0NCg0KZXhpZl90aWR5IDwtIGV4aWZfdGlkeSAlPiUgc2VwYXJhdGUoY3JlYXRlX2RhdGUsIGludG8gPSBjKCdkYXRlJywgJ3RpbWUnKSwgc2VwID0gIiAiLCByZW1vdmUgPSBUUlVFKSAlPiUgc2VwYXJhdGUoZGF0ZSwgaW50byA9IGMoJ3llYXInLCAnbW9udGgnLCAnZGF5JyksIHNlcCA9ICI6IikgDQoNCmV4aWZfdGlkeSRkYXRlIDwtIGFzLkRhdGUocGFzdGUoIjE4ODEiLCBleGlmX3RpZHkkbW9udGgsIGV4aWZfdGlkeSRkYXksIHNlcCA9ICItIiksIGZvcm1hdCA9IiVZLSVtLSVkIikNCmBgYA0KDQoqKkltZ19kYXRhKioNCg0KLSAgIENvdW50IGJ5IGZsaWNrciBpZA0KDQogICAgLSAgIEZsYWcgY291bnRzIHVuZGVyIDEwDQogICAgLSAgIEZsYWcgY291bnRzIHVuZGVyIDYgKGllOiBzdWJpbWFnZSA1IG5vdCBhdmFpbGFibGUpDQogICAgLSAgIE91dHB1dCBsaXN0IG9mIGFsbCBmbGlja3JJRHMgd2l0aCBsZXNzIHRoYW4gMTAgcmVjb3JkcyBmb3IgYWRkaXRpb25hbCAoZnV0dXJlKSBwcm9jZXNzaW5nDQoNCmBgYHtyfQ0KDQpzdWJpbWdfcXR5IDwtIGltZ3NkX3RpZHkgJT4lIGNvdW50KHVzaW5nX2lkKQ0KDQpgYGANCg0KVG8gbXkgKGhhcHB5KSBzdXJwcmlzZSwgb25seSA1IGltYWdlcyBoYXZlIGxlc3MgdGhhbiAxMCByZXN1bHRzIGFuZCBvbmx5IDIgaGF2ZSBsZXNzIHRoYW4gNi4gSW4gdGhlIGludGVyZXN0IG9mIHRpbWUsIEknbSBub3RpbmcgdGhlc2UgSURzIGJ5IGhhbmQgYW5kIHNpbXBseSByZW1vdmluZyB0aGVtIGZyb20gbXkgd29ya2luZyBkYXRhDQoNCmBgYHtyfQ0KDQpnb29kX2lkcyA8LSBzdWJpbWdfcXR5W3N1YmltZ19xdHkkbiA+PTYsICJ1c2luZ19pZCJdDQoNCmltZ3NkX3RpZHkgPC0gaW1nc2RfdGlkeSAlPiUgZmlsdGVyKHVzaW5nX2lkICVpbiUgZ29vZF9pZHMkdXNpbmdfaWQpDQoNCmV4aWZfdGlkeSA8LSBleGlmX3RpZHkgJT4lIGZpbHRlcihmbGlja3JfaWQgJWluJSBnb29kX2lkcyR1c2luZ19pZCkNCg0KYGBgDQoNCi0gICBUb3RhbCBwaXhlbHMgPSBmdWxsXF8oaHVlKVxfY291bnQocykgKGRpbWVuc2lvbiBkYXRhIGluY29ycmVjdCBmb3IgZmlyc3QgMTQwMCByZWNvcmRzKQ0KDQpgYGB7cn0NCg0KaW1nc2RfdGlkeSA8LSBpbWdzZF90aWR5ICU+JSBtdXRhdGUodG90YWxfcGl4ZWxzID0gZnVsbF9yZWRfY291bnQgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsX29yYW5nZV9jb3VudCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGxfeWVsbG93X2NvdW50ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVsbF9ncmVlbl9jb3VudCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGxfY3lhbl9jb3VudCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGxfYmx1ZV9jb3VudCArIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsX3B1cnBsZV9jb3VudCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGxfbWFnX2NvdW50KQ0KDQpgYGANCg0KLSAgIFNwbGl0IHBpeGVsIGxpc3RzL3R1cGxlcyBmb3IgMy1kIG1hcHBpbmcNCg0KYGBge3J9DQojIHJlbW92ZSBicmFja2V0cyBmcm9tIHN0cmluZy1lbmNhcHN1bGF0ZWQgbGlzdHMNCmltZ3NkX3RpZHkkY2VudGVyX2hzbCA8LSBzdHJfcmVwbGFjZShpbWdzZF90aWR5JGNlbnRlcl9oc2wsICdcXFt8XFxdJywgJycpDQoNCiMgcmVtb3ZlIHBhcmVucyBmcm9tIHN0cmluZy1lbmNhcHN1bGF0ZWQgdHVwbGVzDQppbWdzZF90aWR5IDwtIGltZ3NkX3RpZHkgJT4lIG11dGF0ZV9hbGwofiBnc3ViKCdcXCh8XFwpJywgJycsIC4pKQ0KDQojIHNlcGFyYXRlIHBpeGVsIHZhbHVlcyBpbnRvIGluZGl2aWR1YWwgY29sdW1ucw0KaW1nc2Rfc3BsaXQgPC0gaW1nc2RfdGlkeSAlPiUgDQogIHNlcGFyYXRlKGNlbnRlcl9yZ2IsIA0KICAgICAgICAgICBpbnRvID0gYygnY2VudGVyX3InLCAnY2VudGVyX2cnLCAnY2VudGVyX2InKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKHBvc3RfdG9wX2hzbCwgDQogICAgICAgICAgIGludG8gPSBjKCdwb3N0X3RvcF9odWUnLCAncG9zdF90b3Bfc2F0JywgJ3Bvc3RfdG9wX2xpZ2h0JyksIA0KICAgICAgICAgICBzZXAgPSAnLCcpICU+JQ0KICBzZXBhcmF0ZShwb3N0XzJfaHNsLCANCiAgICAgICAgICAgaW50byA9IGMoJ3Bvc3RfMl9odWUnLCAncG9zdF8yX3NhdCcsICdwb3N0XzJfbGlnaHQnKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKHBvc3RfM19oc2wsIA0KICAgICAgICAgICBpbnRvID0gYygncG9zdF8zX2h1ZScsICdwb3N0XzNfc2F0JywgJ3Bvc3RfM19saWdodCcpLCANCiAgICAgICAgICAgc2VwID0gJywnKSAlPiUgIA0KICBzZXBhcmF0ZShwb3N0XzRfaHNsLCANCiAgICAgICAgICAgaW50byA9IGMoJ3Bvc3RfNF9odWUnLCAncG9zdF80X3NhdCcsICdwb3N0XzRfbGlnaHQnKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKHBvc3RfNV9oc2wsIA0KICAgICAgICAgICBpbnRvID0gYygncG9zdF81X2h1ZScsICdwb3N0XzVfc2F0JywgJ3Bvc3RfNV9saWdodCcpLCANCiAgICAgICAgICAgc2VwID0gJywnKSAlPiUNCiAgc2VwYXJhdGUocG9zdF82X2hzbCwgDQogICAgICAgICAgIGludG8gPSBjKCdwb3N0XzZfaHVlJywgJ3Bvc3RfNl9zYXQnLCAncG9zdF82X2xpZ2h0JyksIA0KICAgICAgICAgICBzZXAgPSAnLCcpICU+JQ0KICBzZXBhcmF0ZShjZW50ZXJfaHNsLCANCiAgICAgICAgICAgaW50byA9IGMoJ2NlbnRlcl9odWUnLCAnY2VudGVyX3NhdCcsICdjZW50ZXJfbGlnaHQnKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKGNvbW1vbl9oc2xfMV92YWwsIA0KICAgICAgICAgICBpbnRvID0gYygnY29tbW9uX2hzbF8xX2h1ZScsICdjb21tb25faHNsXzFfc2F0JywgJ2NvbW1vbl9oc2xfMV9saWdodCcpLCANCiAgICAgICAgICAgc2VwID0gJywnKSAlPiUNCiAgc2VwYXJhdGUoY29tbW9uX2hzbF8yX3ZhbCwgDQogICAgICAgICAgIGludG8gPSBjKCdjb21tb25faHNsXzJfaHVlJywgJ2NvbW1vbl9oc2xfMl9zYXQnLCAnY29tbW9uX2hzbF8yX2xpZ2h0JyksIA0KICAgICAgICAgICBzZXAgPSAnLCcpICU+JQ0KICBzZXBhcmF0ZShjb21tb25faHNsXzNfdmFsLCANCiAgICAgICAgICAgaW50byA9IGMoJ2NvbW1vbl9oc2xfM19odWUnLCAnY29tbW9uX2hzbF8zX3NhdCcsICdjb21tb25faHNsXzNfbGlnaHQnKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKGNvbW1vbl9oc2xfNF92YWwsIA0KICAgICAgICAgICBpbnRvID0gYygnY29tbW9uX2hzbF80X2h1ZScsICdjb21tb25faHNsXzRfc2F0JywgJ2NvbW1vbl9oc2xfNF9saWdodCcpLCANCiAgICAgICAgICAgc2VwID0gJywnKQ0KDQpgYGANCg0KLi4uIEkgcHJvYmFibHkgc2hvdWxkIGhhdmUgc2F2ZWQgdGhvc2UgaW5kZXBlbmRlbnRseSBkdXJpbmcgdGhlIHB5dGhvbiBpbWFnZSBwcm9jZXNzaW5nIHN0YWdlLg0KDQotICAgVG8gYmUgYWRkZWQgYXMgbmVlZGVkOg0KDQogICAgLSAgIFVzaW5nIHRvdGFsX3BpeGVscyB0byBjYWxjdWxhdGUgc3BlY2lmaWMgcmF0aW9zDQoNCiMjIEV4cGxvcmF0aW9uDQoNCiMjIyAyRCBTY2F0dGVyDQoNCmBgYHtyfQ0KY3VzdG9tX2NvbG9ycyA9IGMoJyNkZTRiY2QnLCcjOGYyNDExJywgJyNmNWIwNDknLCAnIzljOGExYycsICcjYjNlMzJkJywgICcjMWE5NjE1JywgJyMwNWZmYjQnLCcjMDU1ZWFiJywgJyM2YjYzZmYnLCcjNDUwMDhmJykNCg0KaW1nc2Rfc3BsaXQkY29tbW9uX2hzbF80X2h1ZSA8LSBhcy5udW1lcmljKGltZ3NkX3NwbGl0JGNvbW1vbl9oc2xfNF9odWUpDQppbWdzZF9zcGxpdCRjb21tb25faHNsXzRfc2F0IDwtIGFzLm51bWVyaWMoaW1nc2Rfc3BsaXQkY29tbW9uX2hzbF80X3NhdCkNCg0KZmlnIDwtIHBsb3RfbHkoaW1nc2Rfc3BsaXQsIA0KICAgICAgICAgICAgICAgeD1+Y29tbW9uX2hzbF80X2h1ZSwgDQogICAgICAgICAgICAgICB5PSB+Y29tbW9uX2hzbF80X3NhdCwgDQogICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIiLCBtb2RlPSJtYXJrZXJzIiwgc2l6ZSA9IDIsIGNvbG9yID0gfnN1Yl9pbWcsIGNvbG9ycyA9IGN1c3RvbV9jb2xvcnMpDQoNCmZpZw0KDQpgYGANCg0KT2JzZXJ2YXRpb246IEhlcmUgd2UgY2FuIG1vcmUgY2xvc2VseSBleGFtaW5lIHRoZSBodWUgdHJlbmRzIHByZXNlbnQgaW4gdGhlIGltYWdlcy4gSSdtIGZhc2NpbmF0ZWQgYnkgdGhlIGNsdXN0ZXIgYXQgMTA1IGh1ZS82MC04MCBzYXR1cmF0aW9uIHdoaWNoIGlzIHByZXNlbnQgaW4gbWFueSBzdWItaW1hZ2VzIGJ1dCBub3QgMywgOSwgb3IgNi4NCg0KLSAgIExpZ2h0IG1pbiB4IGxpZ2h0IG1heA0KDQpgYGB7cn0NCg0KaW1nc2Rfc3BsaXQkbGlnaHRfbWluX3ZhbCA8LSBhcy5udW1lcmljKGltZ3NkX3NwbGl0JGxpZ2h0X21pbl92YWwpDQppbWdzZF9zcGxpdCRsaWdodF9tYXhfdmFsIDwtIGFzLm51bWVyaWMoaW1nc2Rfc3BsaXQkbGlnaHRfbWF4X3ZhbCkNCg0KZmlnIDwtIHBsb3RfbHkoaW1nc2Rfc3BsaXQsIA0KICAgICAgICAgICAgICAgeD1+bGlnaHRfbWluX3ZhbCwgDQogICAgICAgICAgICAgICB5PSB+bGlnaHRfbWF4X3ZhbCwgDQogICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIiLCBtb2RlPSJtYXJrZXJzIiwgDQogICAgICAgICAgICAgICBzaXplID0gMiwgY29sb3IgPSB+c3ViX2ltZywgY29sb3JzID0gY3VzdG9tX2NvbG9ycykNCg0KZmlnDQoNCmBgYA0KDQotICAgR2VuIGJyaWdodCB4IGdlbiBkYXJrDQoNCmBgYHtyfQ0KaW1nc2Rfc3BsaXQkZ2VuX2JyaWdodF9jb3VudCA8LSBhcy5pbnRlZ2VyKGltZ3NkX3NwbGl0JGdlbl9icmlnaHRfY291bnQpDQoNCg0KaW1nc2Rfc3BsaXQkZ2VuX2RhcmtfY291bnQgPC0gYXMuaW50ZWdlcihpbWdzZF9zcGxpdCRnZW5fZGFya19jb3VudCkNCg0KZmlnIDwtIHBsb3RfbHkoaW1nc2Rfc3BsaXQsIA0KICAgICAgICAgICAgICAgeD1+Z2VuX2JyaWdodF9jb3VudCwgDQogICAgICAgICAgICAgICB5PSB+Z2VuX2RhcmtfY291bnQsIA0KICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyIiwgbW9kZT0ibWFya2VycyIsIA0KICAgICAgICAgICAgICAgc2l6ZSA9IDIsIGNvbG9yID0gfnN1Yl9pbWcsIGNvbG9ycyA9IGN1c3RvbV9jb2xvcnMpDQoNCmZpZw0KYGBgDQoNCi0gICBTYXQgbWluIHggc2F0IG1heA0KDQpgYGB7cn0NCg0KaW1nc2Rfc3BsaXQkc2F0X21pbl92YWwgPC0gYXMubnVtZXJpYyhpbWdzZF9zcGxpdCRzYXRfbWluX3ZhbCkNCmltZ3NkX3NwbGl0JHNhdF9tYXhfdmFsIDwtIGFzLm51bWVyaWMoaW1nc2Rfc3BsaXQkc2F0X21heF92YWwpDQoNCmZpZyA8LSBwbG90X2x5KGltZ3NkX3NwbGl0LCANCiAgICAgICAgICAgICAgIHg9fnNhdF9taW5fdmFsLCANCiAgICAgICAgICAgICAgIHk9IH5zYXRfbWF4X3ZhbCwgDQogICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIiLCBtb2RlPSJtYXJrZXJzIiwgDQogICAgICAgICAgICAgICBzaXplID0gMiwgY29sb3IgPSB+c3ViX2ltZywgY29sb3JzID0gY3VzdG9tX2NvbG9ycykNCg0KZmlnDQoNCmBgYA0K